package org.particles.domain.world;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.particles.domain.Particle;
import org.particles.domain.Vector;
import org.particles.domain.Vector2D;
import org.particles.domain.field.Field;

public class World2D implements World {
	/**
	 * all particles existing in this world.
	 */
	private List<Particle> particles = new ArrayList<Particle>();
	private List<Particle> particlesCopy = new ArrayList<Particle>();
	/**
	 * world's bounds.
	 */
	private Vector2D dimension;
	private OutOfWorldBoundResolver outOfBoundResolver;
	private FieldRepository fieldRepository;
	
	@Override
	public synchronized void nextState() {
		// first state of each particle will be modified according
		// to each other particle.
		final List<Field> searchResult = new ArrayList<Field>(9);
		Particle p1;
		Particle p2;
		for (int i = 0; i < particles.size(); i++) {
			p2 = particles.get(i);
			for (int j = 0; j < particles.size(); j++) {
				p1 = particlesCopy.get(j);
				if(p1.distanceTo(p2) != 0) {
					fieldRepository.findFields(
							p1.getFields() & p2.getFields(),
							searchResult);
					for(Field field : searchResult) {
						field.applyForces(p1, p2);
					}
					searchResult.clear();
				}
			}
		}
		// we change particle place according to his impulse
		Particle particle = null;
		for (int i = 0; i < particles.size(); i++) {
			particle = particles.get(i);
			particle.getSpacePlace().add(particle.getImpulse());
			// if particle place is out of world's bound
			// we should apply some rules for this situation.
			outOfBoundResolver.resolve(this, i);
		}
		
		// now we copy all particle changes to the copy
		for (int i = 0; i < particles.size(); i++) {
			particlesCopy.get(i).copyStateFrom(particles.get(i));
		}
	}

	/**
	 * @return all particles existing in this world. the list is unmodifiable.
	 */
	@Override
	public List<Particle> getParticles() {
		/*
		 * unmodifiable because of particlesCopy property, as
		 * the two lists - particles and particlesCopy -
		 * must have same size to efficiently calculate next state
		 * of this world.
		 */
		return Collections.unmodifiableList(particles);
	}

	public void setFieldRepository(FieldRepository fieldRepository) {
		this.fieldRepository = fieldRepository;
	}

	public void setOutOfBoundResolver(final OutOfWorldBoundResolver outOfBoundResolver) {
		this.outOfBoundResolver = outOfBoundResolver;
	}

	@Override
	public void addParticle(final Particle particle) {
		particles.add(particle);
		particlesCopy.add(particle);
	}

	@Override
	public Vector getDimension() {
		return dimension;
	}

	/**
	 * @throws ClassCastException if dimension is not of type Vector2D.
	 */
	@Override
	public void setDimension(final Vector dimension) {
		this.dimension = (Vector2D) dimension;
	}

	/**
	 * remove some number particles from this world.
	 * @param number number particles to remove.
	 */
	public void removeParticles(final int number) {
		for(int i = 0; i < number; i++) {
			removeParticleAt(0);
		}
	}
	
	@Override
	public void removeParticleAt(final int index) {
		this.particles.remove(index);
		this.particlesCopy.remove(index);
	}
}
